module Installr::Client::Command
  class Scenario < Base
    def create(scenario_name)
      puts "[scenario:create] create scenario #{scenario_name}" if @options[:verbose]
      @log.info("[scenario:create] create scenario #{scenario_name}")

      if @options[:scenario_file]
        script = ''
        if File.exist?(@options[:scenario_file])
          file = File.new(@options[:scenario_file], "r")
          file.each_line { |line| script += line }
        else
          puts "[scenario:create] cannot open the scenario file #{@options[:scenario_file]}"
          @log.error("[scenario:create] cannot open the scenario file #{@options[:scenario_file]}")
          return -1
        end
      else
        puts "[scenario:create] missing the --scenario-file option"
        @log.error("[scenario:create] missing the --scenario-file option")
        return -1
      end

      if @storage.create_scenario(scenario_name, script) != 0
        puts "[scenario:create] cannot create scenario #{scenario_name}"
        @log.error("[scenario:create] cannot create scenario #{scenario_name}")
        return -1
      end
      return 0
    end

    def delete(scenario_name)
      puts "[scenario:delete] delete scenario #{scenario_name}" if @options[:verbose]
      @log.info("[scenario:delete] delete scenario #{scenario_name}")

     if @storage.delete_scenario(scenario_name) != 0
        puts "[scenario:delete] cannot delete scenario #{scenario_name}"
        @log.error("[scenario:delete] cannot delete scenario #{scenario_name}")
        return -1
      end
      return 0
    end

    def info(scenario_name)
      puts "[scenario:info] info scenario #{scenario_name}" if @options[:verbose]
      @log.info("[scenario:info] info scenario #{scenario_name}")

      scenario = @storage.info_scenario(scenario_name)
      if scenario
        puts "script of the scenario #{scenario_name}: "
        puts "-- start of script --"
        scenario.each { |field| print "#{field} " }
        puts "-- end of script --"
      else
        puts "[scenario:info] cannot find scenario #{scenario_name}"
        @log.error("[scenario:info] cannot find scenario #{scenario_name}")
        return -1
      end
      return 0
    end

    def list
      puts "[scenario:list] list scenarios" if @options[:verbose]
      @log.info("[scenario:list] list scenarios")

      scenarios = @storage.list_scenarios(nil)
      if scenarios
        puts "current scenario listings: "
        scenarios.each { |scenario| print "#{scenario} " }
        puts " ---"
      else
        puts "[scenario:list] cannot find scenario listings"
        @log.error("[scenario:list] cannot find scenario listings")
        return -1
      end
      return 0
    end

    def list_from_schedule(schedule_name)
      puts "[scenario:list_from_schedule] list scenarios from schedule #{schedule_name}" if @options[:verbose]
      @log.info("[scenario:list_from_schedule] list scenarios from schedule #{schedule_name}")

      scenarios = @storage.list_scenarios(schedule_name)
      if scenarios
        puts "current scenario listings from schedule #{schedule_name}: "
        scenarios.each { |scenario| print "#{scenario} " }
        puts " ---"
      else
        puts "[scenario:list_from_schedule] cannot find schedule #{schedule_name}"
        @log.error("[scenario:list_from_schedule] cannot find schedule #{schedule_name}")
      end
      return 0
    end

    def ssh_agent(line)
      ssh_cmd = line.split(' ', 3)
      puts "[scenario:ssh_agent] *** ssh-agent #{ssh_cmd[1]}: [#{ssh_cmd[2]}]"
      @log.info("[scenario:ssh_agent] *** ssh-agent #{ssh_cmd[1]}: [#{ssh_cmd[2]}]")

      agent = @storage.info_agent(ssh_cmd[1])
      unless agent
        puts "[scenario:ssh_agent] cannot find agent #{ssh_cmd[1]}"
        @log.error("[scenario:ssh_agent] cannot find agent #{ssh_cmd[1]}")
        return -1
      end

      
      if (ssh_cmd[2] == nil) or (ssh_cmd[2] == '')
        puts "[scenario:ssh_agent] #{ssh_cmd[1]}: cannot find remote shell command"
        @log.error("[scenario:ssh_agent] #{ssh_cmd[1]}: cannot find remote shell command")
        return -1
      end

      unless port_open?(agent[0], 22)
        puts "[scenario:ssh_agent] #{ssh_cmd[1]}: ssh port cannot be connected"
        @log.error("[scenario:ssh_agent] #{ssh_cmd[1]}: ssh port cannot be connected")
        return -2
      end

      shell = Installr::SSH::SHELL.new(@options)
      if agent[3]
        case shell.open_with_cert(agent[0], agent[1], agent[3], agent[4])
          when -1
            puts "[scenario:ssh_agent] cannot open agent #{ssh_cmd[1]} with certification"
            @log.error("[scenario:ssh_agent] cannot open agent #{ssh_cmd[1]} with certification")
            return -1
          when -2
            puts "[scenario:ssh_agent] authentication with certification for #{ssh_cmd[1]} failed"
            @log.error("[scenario:ssh_agent] authentication with certification for #{ssh_cmd[1]} failed")
            return -1
        end
      else
        case shell.open(agent[0], agent[1], agent[2])
          when -1
            puts "[scenario:ssh_agent] cannot open agent #{ssh_cmd[1]} with certification"
            @log.error("[scenario:ssh_agent] cannot open agent #{ssh_cmd[1]} with certification")
            return -1
          when -2
            puts "[scenario:ssh_agent] authentication with certification for #{ssh_cmd[1]} failed"
            @log.error("[scenario:ssh_agent] authentication with certification for #{ssh_cmd[1]} failed")
            return -1
        end
      end
      exit_code, result = shell.exec(ssh_cmd[2])
      puts result
      shell.close
      return 0

      rescue => e
        puts "[scenario:ssh_agent] #{ssh_cmd[1]}: (exception) #{e}"
        @log.error("[scenario:ssh_agent] #{ssh_cmd[1]}: (exception) #{e}")
        return -2
    end

    def sftp_agent(line)
      ssh_cmd = line.split(' ', 4)
      puts "[scenario:sftp_agent] *** sftp-agent #{ssh_cmd[1]}: [#{ssh_cmd[2]}], [#{ssh_cmd[3]}]"
      @log.info("[scenario:sftp_agent] *** sftp-agent #{ssh_cmd[1]}: [#{ssh_cmd[2]}], [#{ssh_cmd[3]}]")

      agent = @storage.info_agent(ssh_cmd[1])

      unless agent
        puts "[scenario:sftp_agent] cannot find agent #{ssh_cmd[1]}"
        @log.error("[scenario:sftp_agent] cannot find agent #{ssh_cmd[1]}")
        return -1
      end

      if (ssh_cmd[3] == nil) or (ssh_cmd[3] == '')
        puts "[scenario:sftp_agent] cannot find local source path"
        @log.error("[scenario:sftp_agent] cannot find local source path")
        return -1
      end

      if (ssh_cmd[2] == nil or ssh_cmd[2] == '') and (ssh_cmd[3] == nil or ssh_cmd[3] == '')
        puts "[scenario:sftp_agent] cannot find local source path and remote target path"
        @log.error("[scenario:sftp_agent] cannot find local source path and remote target path")
        return -1
      end

      unless port_open?(agent[0], 22)
        puts "[scenario:sftp_agent] #{ssh_cmd[1]}: ssh port cannot be connected"
        @log.error("[scenario:sftp_agent] #{ssh_cmd[1]}: ssh port cannot be connected")
        return -2
      end

      ftp = Installr::SSH::FTP.new(@options)
      if agent[3]
        case ftp.open_with_cert(agent[0], agent[1], agent[3], agent[4])
          when -1
            puts "[scenario:sftp_agent] cannot open agent #{ssh_cmd[1]} with certification"
            @log.error("[scenario:sftp_agent] cannot open agent #{ssh_cmd[1]} with certification")
            return -1
          when -2
            puts "[scenario:sftp_agent] authentication with certification for #{ssh_cmd[1]} failed"
            @log.error("[scenario:sftp_agent] authentication with certification for #{ssh_cmd[1]} failed")
            return -1
        end
      else
        case ftp.open(agent[0], agent[1], agent[2])
          when -1
            puts "[scenario:sftp_agent] cannot open agent #{ssh_cmd[1]} with certification"
            @log.error("[scenario:sftp_agent] cannot open agent #{ssh_cmd[1]} with certification")
            return -1
          when -2
            puts "[scenario:sftp_agent] authentication with certification for #{ssh_cmd[1]} failed"
            @log.error("[scenario:sftp_agent] authentication with certification for #{ssh_cmd[1]} failed")
            return -1
        end
      end
      case ftp.upload(ssh_cmd[2], ssh_cmd[3])
        when -2
          puts "[scenario:sftp_agent] cannot find source file #{ssh_cmd[2]}"
          @log.error("[scenario:sftp_agent] cannot find source file #{ssh_cmd[2]}")
          return -1
        when -1
          puts "[scenario:sftp_agent] cannot upload #{ssh_cmd[2]} to #{ssh_cmd[3]}"
          @log.error("[scenario:sftp_agent] cannot upload #{ssh_cmd[2]} to #{ssh_cmd[3]}")
          return -1
      end
      ftp.close
      return 0

      rescue => e
        puts "[scenario:sftp_agent] #{ssh_cmd[1]}: (exception) #{e}"
        @log.error("[scenario:sftp_agent] #{ssh_cmd[1]}: (exception) #{e}")
        return -2
    end

    def execute(scenario_name)
      puts "[scenario:execute] execute scenario" if @options[:verbose]
      @log.info("[scenario:execute] execute scenario")

      scenario = @storage.info_scenario(scenario_name)
      if scenario
        scenario[0].each_line do |line|
          # remove newline at the last position
          line.chomp!
          if line.chars.first == '#'
            next
          end
          cmd = line.strip.split(' ', 2)
	  case cmd[0]
            when 'exec'
              puts '*** exec: ' + cmd[1]
              IO.popen(cmd[1]) do |pipe|
                pipe.sync = true
                while str = pipe.gets
                  puts str
                end
              end

            when 'ssh-agent'
              ssh_agent(line)

            when 'ssh-group'
              ssh_cmd = line.split(' ', 3)
              puts "[scenario:execute] *** ssh-group #{ssh_cmd[1]}: [#{ssh_cmd[2]}]" if @options[:verbose]
              @log.info("[scenario:execute] *** ssh-group #{ssh_cmd[1]}: [#{ssh_cmd[2]}]")
              agents = @storage.list_agents(ssh_cmd[1])
              if agents
                arr = []
                i = 0
		puts "-- ssh-group result -----"
                agents.each do
                  |agent_name|
                  agent_line = "ssh-agent #{agent_name} #{ssh_cmd[2]}"
                  arr[i] = Thread.new {
                    Thread.current["agent"] = agent_name
                    Thread.current["result"] = ssh_agent(agent_line)
		  }
		  i = i + 1
                  arr.each { |t| t.join }
                  arr.each do
                    |t|
                    if t["result"] == -2
                      puts "[scenario:execute] reexecuting ssh agent #{t["agent"]} ..."
                      @log.info("[scenario:execute] reexecuting ssh agent #{t["agent"]} ...")
                      ssh_agent(agent_line)
                    end
                  end
                  puts "---------------------"
                end
              else
                puts "[scenario:execute] cannot find group #{ssh_cmd[1]}"
	        @log.error("[scenario:execute] cannot find group #{ssh_cmd[1]}")
              end

            when 'sftp-agent'
              sftp_agent(line)

            when 'sftp-group'
              ssh_cmd = line.split(' ', 4)
              puts "[scenario:execute] *** sftp-group #{ssh_cmd[1]}: [#{ssh_cmd[2]}], [#{ssh_cmd[3]}]" if @options[:verbose]
              @log.info("[scenario:execute] *** sftp-group #{ssh_cmd[1]}: [#{ssh_cmd[2]}], [#{ssh_cmd[3]}]")
              agents = @storage.list_agents(ssh_cmd[1])
              if agents
                arr = []
                i = 0
		puts "-- sftp-group result -----"
                agents.each do
                  |agent_name|
                  agent_line = "sftp-agent #{agent_name} #{ssh_cmd[2]} #{ssh_cmd[3]}"
                  arr[i] = Thread.new {
                    Thread.current["agent"] = agent_name
                    Thread.current["result"] = sftp_agent(agent_line)
		  }
		  i = i + 1
                  arr.each { |t| t.join }
                  arr.each do
                    |t|
                    if t["result"] == -2
                      puts "[scenario:execute] reexecuting sftp agent #{t["agent"]} ..."
                      @log.info("[scenario:execute] reexecuting sftp agent #{t["agent"]} ...")
                      sftp_agent(agent_line)
                    end
                  end
                  puts "---------------------"
                end
              else
                puts "[scenario:execute] cannot find group #{ssh_cmd[1]}"
	        @log.error("[scenario:execute] cannot find group #{ssh_cmd[1]}")
              end

            when 'Installr'
              puts "[scenario:execute] *** Installr: #{cmd[1]}" if @options[:verbose]
              @log.info("[scenario:execute] *** Installr: #{cmd[1]}")
              IO.popen('ruby -S ' + line) do |pipe|
                pipe.sync = true
                while str = pipe.gets
                  puts str
                end
              end
            else
              puts "[scenario:execute] unknown command #{cmd[0]}"
              @log.warn("[scenario:execute] unknown command #{cmd[0]}")
	  end
	end
        return 0
      else
        puts "[scenario:execute] cannot find scenario #{scenario_name}"
        @log.error("[scenario:execute] cannot find scenario #{scenario_name}")
        return -1
      end
    end

    def add(scenario_name, schedule_name)
      puts "[scenario:add] add scenario #{scenario_name} to schedule #{schedule_name}" if @options[:verbose]
      @log.info("[scenario:add] add scenario #{scenario_name} to schedule #{schedule_name}")

      if @storage.add_scenario(scenario_name, schedule_name) != 0
        puts "[scenario:add] cannot add scenario #{scenario_name} to schedule #{schedule_name}"
	@log.error("[scenario:add] cannot add scenario #{scenario_name} to schedule #{schedule_name}")
	return -1
      end
      return 0
    end

    def remove(scenario_name, schedule_name)
      puts "[scenario:remove] remove scenario #{scenario_name} from schedule #{schedule_name}" if @options[:verbose]
      @log.info("[scenario:remove] remove scenario #{scenario_name} from schedule #{schedule_name}")

      if @storage.remove_scenario(scenario_name, schedule_name) != 0
        puts "[scenario:remove] cannot remove scenario #{scenario_name} from schedule #{schedule_name}"
	@log.error("[scenario:remove] cannot remove scenario #{scenario_name} from schedule #{schedule_name}")
	return -1
      end
      return 0
    end
  end
end
